package cn.aws360.boot.system.biz.service.impl;

import cn.aws360.boot.base.api.constant.enums.DelFlagEnum;
import cn.aws360.boot.base.api.constant.enums.SysFlagEnum;
import cn.aws360.boot.base.api.exception.ServiceException;
import cn.aws360.boot.data.api.constant.DataConstant;
import cn.aws360.boot.data.api.factory.PageFactory;
import cn.aws360.boot.data.api.pojo.request.PageQuery;
import cn.aws360.boot.data.api.pojo.response.PageResult;
import cn.aws360.boot.data.api.utils.PojoUtils;
import cn.aws360.boot.system.api.constant.SysConstant;
import cn.aws360.boot.system.api.exception.SysExceptionEnum;
import cn.aws360.boot.system.api.pojo.request.SysDictItemQuery;
import cn.aws360.boot.system.api.pojo.request.SysDictQuery;
import cn.aws360.boot.system.api.pojo.request.SysDictWrite;
import cn.aws360.boot.system.api.pojo.response.SysDictItemModel;
import cn.aws360.boot.system.api.pojo.response.SysDictModel;
import cn.aws360.boot.system.biz.domain.SysDict;
import cn.aws360.boot.system.biz.domain.SysDictItem;
import cn.aws360.boot.system.biz.mapper.SysDictMapper;
import cn.aws360.boot.system.biz.service.SysDictItemService;
import cn.aws360.boot.system.biz.service.SysDictService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author junnan
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> implements SysDictService {

    private final CacheManager cacheManager;
    private final SysDictItemService dictItemService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_DICT_CACHE, allEntries = true)
    public Boolean add(SysDictWrite sysDictWrite) {
        SysDict sysDict = BeanUtil.copyProperties(sysDictWrite, SysDict.class, DataConstant.ID);
        checkCodeHasExist(sysDict);
        return this.save(sysDict);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_DICT_CACHE, allEntries = true)
    public Boolean edit(SysDictWrite sysDictWrite) {
        SysDict sysDict = this.getSysDictById(sysDictWrite.getId());
        BeanUtil.copyProperties(sysDictWrite, sysDict);
        checkCodeHasExist(sysDict);
        return this.updateById(sysDict);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_DICT_CACHE, allEntries = true)
    public Boolean remove(String ids) {
        List<Integer> dictIdList = Convert.toList(Integer.class, ids.split(StrUtil.COMMA));
        List<SysDict> sysDictList = this.listByIds(dictIdList);
        for (SysDict sysDict : sysDictList) {
            checkSysDontDelete(sysDict);
            checkHasItemDontDelete(sysDict);
        }
        return this.removeByIds(dictIdList);
    }

    @Override
    public SysDictModel detail(Integer id) {
        SysDict sysDict = this.getSysDictById(id);
        return BeanUtil.copyProperties(sysDict, SysDictModel.class);
    }

    @Override
    public List<SysDictModel> findList(SysDictQuery sysDictQuery) {
        LambdaQueryWrapper<SysDict> queryWrapper = createQueryWrapper(sysDictQuery);
        List<SysDict> sysDictList = this.list(queryWrapper);
        return PojoUtils.copyToList(sysDictList, SysDictModel.class);
    }

    @Override
    public PageResult<SysDictModel> findPage(SysDictQuery sysDictQuery, PageQuery pageQuery) {
        LambdaQueryWrapper<SysDict> queryWrapper = createQueryWrapper(sysDictQuery);
        PageResult<SysDict> sysDictPage = PageFactory.getPageResult(this, pageQuery, queryWrapper);
        return PojoUtils.copyToPage(sysDictPage, SysDictModel.class);
    }

    @Override
    @PostConstruct
    public Boolean refreshCache() {
        Cache cache = cacheManager.getCache(SysConstant.MODULE_DICT_CACHE);
        assert cache != null;
        cache.clear();
        for (SysDict sysDict : this.list()) {
            String code = sysDict.getCode();
            cache.put("SEL_" + code, this.findItemSelByCode(code));
            cache.put("MAP_" + code, this.findItemMapByCode(code));
        }
        return true;
    }

    @Override
    @Cacheable(value = SysConstant.MODULE_DICT_CACHE, key = "'SEL_' + #code")
    public List<SysDictItemModel> findItemSelByCode(String code) {
        SysDict sysDict = this.getOne(Wrappers.<SysDict>lambdaQuery().eq(SysDict::getCode, code));
        checkNotExist(sysDict);
        SysDictItemQuery sysDictItemQuery = SysDictItemQuery.builder().pid(sysDict.getId()).build();
        return dictItemService.findList(sysDictItemQuery);
    }

    @Override
    @Cacheable(value = SysConstant.MODULE_DICT_CACHE, key = "'MAP_' + #code")
    public Map<Integer, String> findItemMapByCode(String code) {
        Map<Integer, String> itemMap = new HashMap<>(8);
        for (SysDictItemModel itemModel : this.findItemSelByCode(code)) {
            itemMap.put(itemModel.getValue(), itemModel.getLabel());
        }
        return itemMap;
    }

    private SysDict getSysDictById(Integer id) {
        SysDict sysDict = this.getById(id);
        checkNotExist(sysDict);
        return sysDict;
    }

    private LambdaQueryWrapper<SysDict> createQueryWrapper(SysDictQuery dictQuery) {
        return Wrappers.<SysDict>lambdaQuery()
                .like(ObjectUtil.isNotEmpty(dictQuery.getName()), SysDict::getName, dictQuery.getName())
                .like(ObjectUtil.isNotEmpty(dictQuery.getCode()), SysDict::getCode, dictQuery.getCode())
                .orderByAsc(SysDict::getId);
    }

    private void checkNotExist(SysDict sysDict) {
        if (ObjectUtil.isNull(sysDict) || DelFlagEnum.Y.getValue().equals(sysDict.getDelFlag())) {
            throw new ServiceException(SysExceptionEnum.DICT_NOT_EXIST);
        }
    }

    private void checkCodeHasExist(SysDict sysDict) {
        Integer id = sysDict.getId();
        String code = sysDict.getCode();
        id = (ObjectUtil.isNotNull(id) && id != 0) ? id : -1;
        SysDict existDict = this.getOne(Wrappers.<SysDict>lambdaQuery().eq(SysDict::getCode, code));
        if (ObjectUtil.isNotNull(existDict) && !existDict.getId().equals(id)) {
            throw new ServiceException(SysExceptionEnum.DICT_CODE_HAS_EXIST, code);
        }
    }

    private void checkSysDontDelete(SysDict sysDict) {
        if (SysFlagEnum.Y.getValue().equals(sysDict.getSysFlag())) {
            throw new ServiceException(SysExceptionEnum.DICT_DONT_DELETE_FOR_SYS, sysDict.getId());
        }
    }

    private void checkHasItemDontDelete(SysDict sysDict) {
        Integer dictId = sysDict.getId();
        LambdaQueryWrapper<SysDictItem> queryWrapper = Wrappers.<SysDictItem>lambdaQuery().eq(SysDictItem::getPid, dictId);
        List<SysDictItem> sysDictItemList = dictItemService.list(queryWrapper);
        if (CollUtil.isNotEmpty(sysDictItemList)) {
            throw new ServiceException(SysExceptionEnum.DICT_DONT_DELETE_FOR_HAS_ITEM, dictId);
        }
    }

}
